/*
 * class Parser
 * 
 * Parses the tokens returned from the lexical analyzer.
 * 
 * Update the parser implementation to parse the Decaf
 * language according to the grammar provided. 
 */
public class Parser {

	private Lexer lexer; // lexical analyzer
	private Token token; // look ahead token
	
	public Parser(Lexer lex) {
		lexer = lex;
	}
	
	public boolean parse() {
		
		token = lexer.nextToken();
		
		boolean value;
		
		while(token.getTokenType() != Token.EOF) {
			value = classID();
			
			if(!value)
				return false;
		}
		
		return true;
	}
	
	private boolean classID(){
		boolean value = match(Token.CLASS);	
		if (token.getTokenType() == Token.ID){
				value &= match(token.getTokenType());
				value &= match(Token.LB);
				value &= methodDecls();
				value &= match(Token.RB);
		}else{
			return false;
		}
		return value;
	}
	
	private boolean methodDecls(){
		boolean value = true;
		while (token.getTokenType() != Token.RB){
			value &= methodDecl();
		}
		return value;
	}
	
	private boolean methodDecl(){
		boolean value= true;
		if (token.getTokenType() == Token.STATIC){
			value &= match(token.getTokenType());
			value &= type();
			value &= match(Token.ID);
			value &= match(Token.LP);
			value &= formalParams();
			value &= match(Token.RP);
			value &= block();
		}else{
			return false;
		}
		return value;
	}
	
	private boolean formalParams(){
		boolean value = true;
		value &= properFormalParams();
		return value;
	}
	
	private boolean properFormalParams(){
		boolean value = true;
		while(token.getTokenType() != Token.RP){
			value &= properFormalParam();
			if(token.getTokenType() == Token.FA){
				value &= match(Token.FA);
			}
		}
		
		return value;
	}
	
	private boolean properFormalParam(){
		boolean value = type();
		value &= match(Token.ID);
		return value;
	}
	
	private boolean block(){
		boolean value = match(Token.LB);
		value &= statements();
		value &= match(Token.RB);
		return value;
	}
	
	private boolean statements(){
		boolean value =true;
		while(token.getTokenType()!= Token.RB){
			value &= statement();	
		}
		return value;
	}
	
	private boolean statement(){
		boolean value = true;
		while (true){
			switch(token.getTokenType()){
				case Token.IF:
					value &= ifStmt();
					break;
				case Token.WHILE:
					value &= whileStmt();
					break;
				case Token.ID:
					value &= assignStmt();
					break;
				case Token.RETURN:
					value &= returnStmt();
					break;
				case Token.LB:
					value &= block();
					break;
				case Token.INT:
				case Token.BOOLEAN:
				case Token.FLOAT:
				case Token.STRING:
					value &= localVarDecl();
				default:
					return value;
			}
		}
	}
	
	private boolean returnStmt(){
		return match(Token.RETURN) && expression() && match(Token.SM);
	}
	
	private boolean ifStmt(){
		boolean value = match(Token.IF) && match(Token.LP) && expression() && match(Token.RP) && statement();
		if (token.getTokenType() == Token.ELSE){
			value &= match(Token.ELSE) && statement();
		}
		return value;
	}
	
	private boolean whileStmt(){
		return match(Token.WHILE) && match(Token.LP) && expression() && match(Token.RP) && statement();
	}
	
	private boolean localVarDecl(){
		return type() && match(Token.ID) && match(Token.SM);
	}
	
	private boolean assignStmt(){
		return match(Token.ID) && match(Token.AO) && expression() && match(Token.SM);
	}
	
	private boolean expression(){
		boolean value = conditionalAndExpr();
		while (true){
			switch (token.getTokenType()){
				case Token.LO:
					value &= match(token.getTokenType());
					value &= conditionalAndExpr();
					break;
				default:
					return value;
			}
		}
	}
	
	private boolean conditionalAndExpr(){
		boolean value = equalityExpr();
		while (true){
			switch (token.getTokenType()){
				case Token.LA:
					value &= match(token.getTokenType());
					value &= equalityExpr();
					break;
				default:
					return value;
			}
		}	
	}
	
	private boolean equalityExpr(){
		boolean value = additiveExpr();
		while (true){
			switch (token.getTokenType()){
				case Token.EQ:
					value &= match(token.getTokenType());
					value &= additiveExpr();
					break;
				case Token.NE:
					value &= match(token.getTokenType());
					value &= additiveExpr();
					break;
				default:
					return value;
			}
		}
	}
	
	private boolean additiveExpr(){
		boolean value = multiplictiveExpr();
		while (true){
			switch (token.getTokenType()){
				case Token.MO:
					value &= match(token.getTokenType());
					value &= multiplictiveExpr();
					break;
				case Token.PO:
					value &= match(token.getTokenType());
					value &= multiplictiveExpr();
					break;
				default:
					return value;
			}
		}
	}
	
	private boolean multiplictiveExpr(){
		boolean value = primaryExpr();
		while (true){
			switch (token.getTokenType()){
				case Token.TO:
					value &= match(token.getTokenType());
					value &= primaryExpr();
					break;
				case Token.DO:
					value &= match(token.getTokenType());
					value &= primaryExpr();
					break;
				case Token.MD:
					value &= match(token.getTokenType());
					value &= primaryExpr();
					break;
				default:
					return value;
			}
		}
	}
	
	private boolean primaryExpr(){
		switch (token.getTokenType()){
			case Token.NM:
				return match(token.getTokenType());
			case Token.BL:
				return match(token.getTokenType());
			case Token.ST:
				return match(token.getTokenType());
			case Token.ID:
				match(token.getTokenType());
				if (token.getTokenType() == Token.LP){
					return callExpr();
				}else{
					return true;
				}
			case Token.LP:
				match(token.getTokenType());
				return expression() && match(Token.RP);
			default: 
				return false;
		}
	}
	
	private boolean callExpr(){
		return match(Token.LP) && actualParams() && match(Token.RP);
	}
	
	private boolean type(){
		switch (token.getTokenType()){
			case Token.INT:
				return match(token.getTokenType());
				
			case Token.FLOAT:
				return match(token.getTokenType());
		
			case Token.BOOLEAN:
				return match(token.getTokenType());
		
			case Token.STRING:
				return match(token.getTokenType());
			
			default:
				return false;
		}
	}
	
	private boolean actualParams(){
		return properActualParams();
	}
	
	private boolean properActualParams(){
		boolean value = expression();
		while(true){
			switch(token.getTokenType()){
				case Token.FA:
					value &= match(token.getTokenType());
					value &= expression();
				default:
					return value;
			}
		}
	}

	private boolean match(int t) {
		if (token.getTokenType() == t) {
			token = lexer.nextToken();
			return true;
		} else {
			return false;
		}
	}
}
